//----------------------------------------------------------------------------//
//                                                                            //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation  //
// and distributed under the MIT License (MIT).                               //
//                                                                            //
// Copyright (c) Guillaume Blanc                                              //
//                                                                            //
// Permission is hereby granted, free of charge, to any person obtaining a    //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation  //
// the rights to use, copy, modify, merge, publish, distribute, sublicense,   //
// and/or sell copies of the Software, and to permit persons to whom the      //
// Software is furnished to do so, subject to the following conditions:       //
//                                                                            //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software.                        //
//                                                                            //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        //
// DEALINGS IN THE SOFTWARE.                                                  //
//                                                                            //
//----------------------------------------------------------------------------//

#ifndef OZZ_OZZ_ANIMATION_RUNTIME_TRACK_TRIGGERING_JOB_H_
#define OZZ_OZZ_ANIMATION_RUNTIME_TRACK_TRIGGERING_JOB_H_

#include "SkrAnim/ozz/export.h"
#include "SkrAnim/ozz/base/platform.h"
#include "SkrBase/misc/debug.h" // for SKR_ASSERT

namespace ozz
{
namespace animation
{

class FloatTrack;

// Track edge triggering job implementation. Edge triggering wording refers to
// signal processing, where a signal edge is a transition from low to high or
// from high to low. It is called an "edge" because of the square wave which
// represents a signal has edges at those points. A rising edge is the
// transition from low to high, a falling edge is from high to low.
// TrackTriggeringJob detects when track curve crosses a threshold value,
// triggering dated events that can be processed as state changes.
// Only FloatTrack is supported, because comparing to a threshold for other
// track types isn't possible.
// The job execution actually performs a lazy evaluation of edges. It builds an
// iterator that will process the next edge on each call to ++ operator.
struct OZZ_ANIMATION_DLL TrackTriggeringJob {
    TrackTriggeringJob();

    // Validates job parameters.
    bool Validate() const;

    // Validates and executes job. Execution is lazy. Iterator operator ++ is
    // actually doing the processing work.
    bool Run() const;

    // Input range. 0 is the beginning of the track, 1 is the end.
    // from and to can be of any sign, any order, and any range. The job will
    // perform accordingly:
    // - if difference between from and to is greater than 1, the iterator will
    // loop multiple times on the track.
    // - if from is greater than to, then the track is processed backward (rising
    // edges in forward become falling ones).
    float from;
    float to;

    // Edge detection threshold value.
    // A rising edge is detected as soon as the track value becomes greater than
    // the threshold.
    // A falling edge is detected as soon as the track value becomes smaller or
    // equal than the threshold.
    float threshold;

    // Track to sample.
    const FloatTrack* track;

    // Job output iterator.
    class Iterator;
    Iterator* iterator;

    // Returns an iterator referring to the past-the-end element. It should only
    // be used to test if iterator loop reached the end (using operator !=), and
    // shall not be dereferenced.
    Iterator end() const;

    // Structure of an edge as detected by the job.
    struct Edge {
        float ratio;  // Ratio at which track value crossed threshold.
        bool  rising; // true is edge is rising (getting higher than threshold).
    };
};

// Iterator implementation. Calls to ++ operator will compute the next edge. It
// should be compared (using operator !=) to job's end iterator to test if the
// last edge has been reached.
class OZZ_ANIMATION_DLL TrackTriggeringJob::Iterator
{
public:
    Iterator()
        : job_(nullptr)
        , outer_(0.f)
        , inner_(0)
    {
    }

    // Evaluate next edge.
    // Calling this function on an end iterator results in an assertion in debug,
    // an undefined behavior otherwise.
    const Iterator& operator++();
    Iterator        operator++(int)
    {
        Iterator prev = *this;
        ++*this;
        return prev;
    }

    // Compare with other iterators.
    bool operator!=(const Iterator& _it) const
    {
        return inner_ != _it.inner_ || outer_ != _it.outer_ || job_ != _it.job_;
    }
    bool operator==(const Iterator& _it) const
    {
        return job_ == _it.job_ && outer_ == _it.outer_ && inner_ == _it.inner_;
    }

    // Dereferencing operators.
    const Edge& operator*() const
    {
        SKR_ASSERT(*this != job_->end() && "Can't dereference end iterator.");
        return edge_;
    }
    const Edge* operator->() const
    {
        SKR_ASSERT(*this != job_->end() && "Can't dereference end iterator.");
        return &edge_;
    }

private:
    friend struct TrackTriggeringJob;

    // Constructors used by the job.
    explicit Iterator(const TrackTriggeringJob* _job);
    struct End {
    };
    Iterator(const TrackTriggeringJob* _job, End)
        : job_(_job)
        , outer_(0.f)
        , inner_(-2)
    { // Can never be reached while looping.
    }

    // Job this iterator works on.
    const TrackTriggeringJob* job_ = nullptr;

    // Current value of the outer loop, aka a ratio cursor between from and to.
    float outer_ = 0.f;

    // Current value of the inner loop, aka a key frame index.
    ptrdiff_t inner_ = 0;

    // Latest evaluated edge.
    Edge edge_;
};

// end() job function inline implementation.
inline TrackTriggeringJob::Iterator TrackTriggeringJob::end() const
{
    return Iterator(this, Iterator::End());
}
} // namespace animation
} // namespace ozz
#endif // OZZ_OZZ_ANIMATION_RUNTIME_TRACK_TRIGGERING_JOB_H_
